﻿namespace WebPacker.Packs.Builders
{
	using System;
	using System.Collections.Generic;
	using System.Text;
	using WebPacker.Configuration;
	using WebPacker.Filters;
	using WebPacker.Helpers;
	using WebPacker.Packs.Resources;
	using WebPacker.Packs.Watchers;
	using WebPacker.VersionGenerators;

	public class PackBuilder : IPackBuilder
	{
		private readonly FilterCollection filters;
		private readonly VersionGeneratorCollection generators;
		private readonly IResourceWatcher watcher;
		private bool monitoring;
		private string versionGenerator;

		public PackBuilder()
			: this(null, null, null)
		{
		}

		public PackBuilder(IEnumerable<IFilter> filters, IResourceWatcher resourceWatcher, IEnumerable<IVersionGenerator> versionGenerators)
		{
			this.filters = new FilterCollection();
			this.generators = new VersionGeneratorCollection();
			this.watcher = resourceWatcher;

			if (filters != null)
			{
				this.filters.AddRange(filters);
			}

			if (versionGenerators != null)
			{
				this.generators.AddRange(versionGenerators);
			}
		}

		public ICollection<IFilter> Filters
		{
			get { return filters; }
		}

		public ICollection<IVersionGenerator> VersionGenerators
		{
			get { return generators; }
		}

		public bool DefaultMonitoring
		{
			get { return monitoring; }
			set { monitoring = value; }
		}

		public string DefaultVersionGenerator
		{
			get { return versionGenerator; }
			set { versionGenerator = value; }
		}

		public virtual IPack BuildPack(PackType packType, string[] resources)
		{
			if (resources == null)
			{
				throw new ArgumentNullException("resources");
			}

			var packName = GeneratePackName(packType, resources);
			var resourceInfos = new ResourceInfo[resources.Length];

			for (var i = 0; i < resources.Length; i++)
			{
				if (resources[i] == null)
				{
					throw new ArgumentNullException("Resource cannot be null.");
				}

				resourceInfos[i] = new ResourceInfo
				{
					Name = resources[i]
				};
			}

			return BuildPack(new PackInfo
			{
				Name = packName,
				PackType = packType,
				Resources = resourceInfos,
				VersionGeneratorName = versionGenerator,
				Monitoring = monitoring
			});
		}

		public virtual IPack BuildPack(PackInfo packInfo)
		{
			if (packInfo == null)
			{
				throw new ArgumentNullException("packInfo");
			}

			if (packInfo.Name == null)
			{
				throw new ArgumentNullException("Pack name cannot be null.");
			}

			IFilter filter = null;
			IVersionGenerator generator = null;

			var packName = packInfo.Name;

			if (!string.IsNullOrEmpty(packInfo.FilterName))
			{
				filter = filters.GetItem(packInfo.PackType, packInfo.FilterName);
			}
			else
			{
				filters.TryGetItem(packInfo.PackType, out filter);
			}

			if (string.IsNullOrEmpty(packInfo.VersionGeneratorName))
			{
				if (!string.IsNullOrEmpty(versionGenerator))
				{
					generator = generators.GetItem(versionGenerator);
				}
			}
			else
			{
				generator = generators.GetItem(packInfo.VersionGeneratorName);
			}

			var resources = new IResource[packInfo.Resources.Length];

			for (var i = 0; i < packInfo.Resources.Length; i++)
			{
				resources[i] = BuildResource(packInfo, packInfo.Resources[i]);
			}

			var resourceWatcher = packInfo.Monitoring ?? monitoring ? watcher : null;

			var pack = new Pack(packInfo.PackType, packName, resources, filter, resourceWatcher, generator, packInfo.Version);
			pack.UpdateVersion();

			return pack;
		}

		public virtual string GeneratePackName(PackType packType, string[] resources)
		{
			if (resources == null)
			{
				throw new ArgumentNullException("resources");
			}

			var builder = new StringBuilder(512);
			builder.Append(((int)packType).ToString());

			foreach (var item in resources)
			{
				builder.Append(item);
			}

			return HashHelper.ComputeMd5HashString(builder.ToString());
		}

		protected virtual IResource BuildResource(PackInfo packInfo, ResourceInfo resourceInfo)
		{
			var path = resourceInfo.Name;

			if (path.StartsWith("http://"))
			{
				return new RemoteResource(path);
			}
			else
			{
				if (!string.IsNullOrEmpty(packInfo.BasePath))
				{
					path = VirtualPathHelper.CombineUri(packInfo.BasePath, path);
				}

				var fileName = VirtualPathHelper.ToPhisicalPath(path);

				if (fileName.IndexOfAny(new char[] { '?', '*' }) != -1)
				{
					return new WildcardFileResource(fileName)
					{
						Encoding = resourceInfo.Encoding
					};
				}
				else
				{
					return new FileResource(fileName)
					{
						Encoding = resourceInfo.Encoding
					};
				}
			}
		}
	}
}